package it.infodea.tapestrydea.services.pageinfo.impl;

import it.infodea.tapestrydea.exceptions.PageAccessDeniedException;
import it.infodea.tapestrydea.services.UserSecurityLevelProvider;
import it.infodea.tapestrydea.support.annotations.PageInfo;
import it.infodea.tapestrydea.support.enumerations.SecurityGroup;

import java.io.IOException;

import org.apache.tapestry5.services.ComponentClassResolver;
import org.apache.tapestry5.services.Dispatcher;
import org.apache.tapestry5.services.ExceptionReporter;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.Response;

public class PageInfoAccessControlDispatcher implements Dispatcher {
	
	private ComponentClassResolver resolver;
	private UserSecurityLevelProvider levelProvider;
	
	public PageInfoAccessControlDispatcher(ComponentClassResolver resolver, UserSecurityLevelProvider levelProvider) {
		super();
		this.resolver = resolver;
		this.levelProvider = levelProvider;
	}



	public boolean dispatch(Request request, Response response)
			throws IOException {
		
        // The extended name may include a page activation context. The trick is
        // to figure out where the logical page name stops and where the
        // activation context begins. Further, strip out the leading slash.

        String path = request.getPath();

        // TAPESTRY-1343: Sometimes path is the empty string (it should always be at least a slash,
        // but Tomcat may return the empty string for a root context request).

        String extendedName = path.length() == 0 ? path : path.substring(1);

        // Ignore trailing slashes in the path.
        while (extendedName.endsWith("/"))
            extendedName = extendedName.substring(0, extendedName.length() - 1);

        int slashx = extendedName.length();

        while (slashx > 0)
        {

            String pageName = extendedName.substring(0, slashx);

            if (resolver.isPageName(pageName)){
	            // control page info
            	if(!canAccess(pageName)){
            		throw createException(pageName);
            	}else{
            		return false;
            	}
            }
            slashx = extendedName.lastIndexOf('/', slashx - 1);
        }

        // OK, maybe its all page activation context for the root Index page.
        if(!canAccess("index")){
    		throw createException("index");
    	}
        return false;
    }



	private PageAccessDeniedException createException(String pageName) {
		
		StringBuffer message = new StringBuffer();
		String className = resolver.resolvePageNameToClassName(pageName);

		try {
			Class<?> forName = Class.forName(className);
			PageInfo annotation = forName.getAnnotation(PageInfo.class);
			if(annotation == null){
				message.append("The annotation @PageInfo is required to access page ")
					.append(pageName);
			}else{
				SecurityGroup allowedSecurityGroup = annotation.allowedSecurityGroup();
				int userLevel = levelProvider.provideUserSecurityLevel();
				int pageLevel = allowedSecurityGroup.getLevel();
				if(allowedSecurityGroup != null &&
						(pageLevel < userLevel)){
					

					message.append("The user cannot access to page ")
					.append(pageName)
					.append(" with access level ").append(SecurityGroup.getByLevel(userLevel))
					.append(".\n Level ")
					.append(allowedSecurityGroup.toString())
					.append(" is required.");
				}
			}
		} catch (ClassNotFoundException e) {
			message.append(e.getMessage());
		}

		return new PageAccessDeniedException(message.toString());
	}



	private boolean canAccess(String pageName) {
		String className = resolver.resolvePageNameToClassName(pageName);
		try {
			Class<?> forName = Class.forName(className);
			if(isNotErrorPage(forName)){
				
				PageInfo annotation = forName.getAnnotation(PageInfo.class);
				if(annotation == null){
					return false;
				}
				
				SecurityGroup allowedSecurityGroup = annotation.allowedSecurityGroup();
				if(allowedSecurityGroup != null &&
						(allowedSecurityGroup.getLevel() < levelProvider.provideUserSecurityLevel())){
					
					return false;
				}
			}
			return true;
		} catch (ClassNotFoundException e) {}
		return true;
	}

	private boolean isNotErrorPage(Class<?> pageClass) {
		return !ExceptionReporter.class.isAssignableFrom(pageClass);
	}

}
